#include "opengl_video_renderer.hpp"
#include "third_party/libyuv/include/libyuv/convert_argb.h"

namespace p2pms {

OpenGLVideoRenderer::OpenGLVideoRenderer(const MediaInfo& media, IMainThreadExecutor* executor)
    : m_media_info(media), m_executor(executor) {
    LOG_INF("Constructing OpenGLVideoRenderer");
    m_render_thread = std::thread(&OpenGLVideoRenderer::RenderThreadFunc, this);
}

OpenGLVideoRenderer::~OpenGLVideoRenderer() {
    LOG_INF("Destroying OpenGLVideoRenderer");
    m_stop_thread = true;
    if (m_render_thread.joinable()) {
        m_render_thread.join();
    }
    if (m_gl_initialized) {
        Cleanup();
    }
}

#ifdef _WIN32
bool OpenGLVideoRenderer::SetRenderWindow(HWND hwnd) {
    std::lock_guard<std::mutex> lock(m_mutex);
    LOG_INF("Setting render window: hwnd={}", fmt::ptr(hwnd));

    if (!hwnd) {
        LOG_INF("Setting hwnd to nullptr, stopping rendering");
        Stop();
        Cleanup();
        return true;
    }

    m_hdc = GetDC(hwnd);
    if (!m_hdc) {
        LOG_ERR("GetDC failed");
        return false;
    }

    PIXELFORMATDESCRIPTOR pfd = {
        sizeof(PIXELFORMATDESCRIPTOR), 1,
        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
        PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        24, 8, 0, PFD_MAIN_PLANE, 0, 0, 0, 0
    };
    int pixel_format = ChoosePixelFormat(m_hdc, &pfd);
    if (!pixel_format || !SetPixelFormat(m_hdc, pixel_format, &pfd)) {
        LOG_ERR("SetPixelFormat failed");
        ReleaseDC(hwnd, m_hdc);
        return false;
    }

    m_hglrc = wglCreateContext(m_hdc);
    if (!m_hglrc) {
        LOG_ERR("wglCreateContext failed");
        ReleaseDC(hwnd, m_hdc);
        return false;
    }

    if (!wglMakeCurrent(m_hdc, m_hglrc)) {
        LOG_ERR("wglMakeCurrent failed");
        wglDeleteContext(m_hglrc);
        ReleaseDC(hwnd, m_hdc);
        return false;
    }

    RECT rect;
    GetClientRect(hwnd, &rect);
    m_width = rect.right - rect.left;
    m_height = rect.bottom - rect.top;
    if (m_width <= 0 || m_height <= 0) {
        m_width = m_media_info.width;
        m_height = m_media_info.height;
    }

    LOG_INF("Setting render window size to {}x{}", m_width, m_height);
    InitializeGL();
    m_is_rendering = true;
    return true;
}
#else
bool OpenGLVideoRenderer::SetRenderWindow(void* native_window) {
    std::lock_guard<std::mutex> lock(m_mutex);
    LOG_INF("Setting native window: {}", fmt::ptr(native_window));

    if (!native_window) {
        LOG_INF("Setting native window to nullptr, stopping rendering");
        Stop();
        Cleanup();
        return true;
    }

#ifdef __ANDROID__
    m_egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (!eglInitialize(m_egl_display, nullptr, nullptr)) {
        LOG_ERR("eglInitialize failed");
        return false;
    }

    EGLint config_attributes[] = {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_ALPHA_SIZE, 8,
        EGL_NONE
    };
    EGLConfig config;
    EGLint num_configs;
    if (!eglChooseConfig(m_egl_display, config_attributes, &config, 1, &num_configs)) {
        LOG_ERR("eglChooseConfig failed");
        return false;
    }

    m_egl_surface = eglCreateWindowSurface(m_egl_display, config, static_cast<ANativeWindow*>(native_window), nullptr);
    if (m_egl_surface == EGL_NO_SURFACE) {
        LOG_ERR("eglCreateWindowSurface failed");
        return false;
    }

    EGLint context_attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE };
    m_egl_context = eglCreateContext(m_egl_display, config, EGL_NO_CONTEXT, context_attributes);
    if (m_egl_context == EGL_NO_CONTEXT) {
        LOG_ERR("eglCreateContext failed");
        return false;
    }

    if (!eglMakeCurrent(m_egl_display, m_egl_surface, m_egl_surface, m_egl_context)) {
        LOG_ERR("eglMakeCurrent failed");
        return false;
    }
#endif

#ifdef __APPLE__
    m_eagl_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    if (!m_eagl_context) {
        LOG_ERR("EAGLContext creation failed");
        return false;
    }
    CAEAGLLayer* eagl_layer = (CAEAGLLayer*)[static_cast<UIView*>(native_window) layer];
    eagl_layer.drawableProperties = @{
        kEAGLDrawablePropertyRetainedBacking: @NO,
        kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8
    };
    [EAGLContext setCurrentContext:m_eagl_context];

    glGenFramebuffers(1, &m_framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
    glGenRenderbuffers(1, &m_renderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer);
    [m_eagl_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:eagl_layer];
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_renderbuffer);

    GLint width, height;
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
    m_width = width;
    m_height = height;
#endif

    if (m_width <= 0 || m_height <= 0) {
        m_width = m_media_info.width;
        m_height = m_media_info.height;
    }

    LOG_INF("Setting native window size to {}x{}", m_width, m_height);
    InitializeGL();
    m_is_rendering = true;
    return true;
}
#endif

void OpenGLVideoRenderer::SetVideoTrack(webrtc::VideoTrackInterface* video_track) {
    std::lock_guard<std::mutex> lock(m_mutex);
    LOG_INF("Setting video track: {}", video_track ? video_track->id() : "nullptr");

    if (m_video_track) {
        m_video_track->RemoveSink(this);
        LOG_INF("Removed existing video track sink: {}", m_video_track->id());
    }

    m_video_track = video_track;
    if (video_track) {
        video_track->AddOrUpdateSink(this, rtc::VideoSinkWants());
        LOG_INF("Added video track sink: {}", video_track->id());
    }
}

void OpenGLVideoRenderer::OnPaint() {
    LOG_INF("OnPaint called, triggering update");
    if (m_executor) {
        m_executor->PostToMainThread([this] { ProcessRenderCommands(); });
    } else {
        LOG_ERR("IMainThreadExecutor is null, cannot post render task");
    }
}

void OpenGLVideoRenderer::OnWindowSizeChanged(int width, int height) {
    LOG_INF("OnWindowSizeChanged called: {}x{}", width, height);
    if (m_executor) {
        m_executor->PostToMainThread([this, width, height] {
            std::lock_guard<std::mutex> lock(m_mutex);
            ResizeGL(width, height);
        });
    } else {
        LOG_ERR("IMainThreadExecutor is null, cannot post resize task");
    }
}

void OpenGLVideoRenderer::Start() {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_is_rendering = true;
    LOG_INF("Renderer started");
}

void OpenGLVideoRenderer::Stop() {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_is_rendering = false;
    LOG_INF("Renderer stopped");
}

void OpenGLVideoRenderer::OnFrame(const webrtc::VideoFrame& frame) {
    LOG_INF("Received video frame: id={}, size={}x{}, rotation={}",
            frame.id(), frame.width(), frame.height(), frame.rotation());

    RenderCommand cmd;
    cmd.use_yuv = m_use_yuv;
    auto buffer = frame.video_frame_buffer()->ToI420();
    if (!buffer) {
        LOG_ERR("Failed to get I420 buffer from video frame");
        return;
    }

    if (m_use_yuv) {
        cmd.yuv_buffer = buffer.get();
    } else {
        int width = buffer->width();
        int height = buffer->height();
        cmd.bgra_data.resize(width * height * 4);
        int result = libyuv::I420ToARGB(
            buffer->DataY(), buffer->StrideY(),
            buffer->DataU(), buffer->StrideU(),
            buffer->DataV(), buffer->StrideV(),
            cmd.bgra_data.data(), width * 4,
            width, height);
        if (result != 0) {
            LOG_ERR("I420ToARGB conversion failed: {}", result);
            return;
        }
        cmd.width = width;
        cmd.height = height;
    }

#ifdef __ANDROID__
    // cmd.native_buffer = GetMediaCodecBuffer(); // Assume WebRTC provides ANativeWindow
#endif
#ifdef __APPLE__
    // cmd.pixel_buffer = GetVideoToolboxBuffer(); // Assume WebRTC provides CVPixelBuffer
#endif

    {
        std::lock_guard<std::mutex> lock(m_command_mutex);
        m_command_queue.push(cmd);
    }
}

void OpenGLVideoRenderer::RenderThreadFunc() {
    while (!m_stop_thread) {
        RenderCommand cmd;
        bool has_command = false;
        {
            std::lock_guard<std::mutex> lock(m_command_mutex);
            if (!m_command_queue.empty()) {
                cmd = m_command_queue.front();
                m_command_queue.pop();
                has_command = true;
            }
        }

        if (has_command && m_executor) {
            m_executor->PostToMainThread([this, cmd]() {
                std::lock_guard<std::mutex> lock(m_mutex);
                RenderFrame(cmd);
            });
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(16));
    }
}

void OpenGLVideoRenderer::ProcessRenderCommands() {
    if (!m_is_rendering) {
        LOG_INF("Renderer is not rendering, skipping commands");
        return;
    }
    PaintGL();
}

void OpenGLVideoRenderer::InitializeGL() {
    if (m_gl_initialized) return;

#ifdef _WIN32
    if (!wglMakeCurrent(m_hdc, m_hglrc)) {
        LOG_ERR("wglMakeCurrent failed");
        return;
    }
#endif
#ifdef __ANDROID__
    if (!eglMakeCurrent(m_egl_display, m_egl_surface, m_egl_surface, m_egl_context)) {
        LOG_ERR("eglMakeCurrent failed");
        return;
    }
#endif
#ifdef __APPLE__
    [EAGLContext setCurrentContext:m_eagl_context];
#endif

    const char* vs_code = R"(
        #version 300 es
        in vec3 aPosition;
        in vec2 aTexCoord;
        out vec2 vTexCoord;
        void main() {
            gl_Position = vec4(aPosition, 1.0);
            vTexCoord = aTexCoord;
        }
    )";
    const char* ps_code = R"(
        #version 300 es
        precision mediump float;
        in vec2 vTexCoord;
        uniform sampler2D uTexture;
        out vec4 fragColor;
        void main() {
            fragColor = texture(uTexture, vTexCoord);
        }
    )";
    const char* yuv_ps_code = R"(
        #version 300 es
        precision mediump float;
        in vec2 vTexCoord;
        uniform sampler2D uTextureY;
        uniform sampler2D uTextureU;
        uniform sampler2D uTextureV;
        out vec4 fragColor;
        void main() {
            float y = texture(uTextureY, vTexCoord).r;
            float u = texture(uTextureU, vTexCoord).r - 0.5;
            float v = texture(uTextureV, vTexCoord).r - 0.5;
            float r = y + 1.402 * v;
            float g = y - 0.344 * u - 0.714 * v;
            float b = y + 1.772 * u;
            fragColor = vec4(r, g, b, 1.0);
        }
    )";

    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vs, 1, &vs_code, nullptr);
    glCompileShader(vs);
    GLint status;
    glGetShaderiv(vs, GL_COMPILE_STATUS, &status);
    if (!status) {
        char log[512];
        glGetShaderInfoLog(vs, sizeof(log), nullptr, log);
        LOG_ERR("Vertex shader compilation failed: {}", log);
        return;
    }

    GLuint ps = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(ps, 1, &ps_code, nullptr);
    glCompileShader(ps);
    glGetShaderiv(ps, GL_COMPILE_STATUS, &status);
    if (!status) {
        char log[512];
        glGetShaderInfoLog(ps, sizeof(log), nullptr, log);
        LOG_ERR("Fragment shader compilation failed: {}", log);
        return;
    }

    m_program = glCreateProgram();
    glAttachShader(m_program, vs);
    glAttachShader(m_program, ps);
    glLinkProgram(m_program);
    glGetProgramiv(m_program, GL_LINK_STATUS, &status);
    if (!status) {
        char log[512];
        glGetProgramInfoLog(m_program, sizeof(log), nullptr, log);
        LOG_ERR("Program linking failed: {}", log);
        return;
    }
    glDeleteShader(ps);

    ps = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(ps, 1, &yuv_ps_code, nullptr);
    glCompileShader(ps);
    glGetShaderiv(ps, GL_COMPILE_STATUS, &status);
    if (!status) {
        char log[512];
        glGetShaderInfoLog(ps, sizeof(log), nullptr, log);
        LOG_ERR("YUV fragment shader compilation failed: {}", log);
        return;
    }

    m_yuv_program = glCreateProgram();
    glAttachShader(m_yuv_program, vs);
    glAttachShader(m_yuv_program, ps);
    glLinkProgram(m_yuv_program);
    glGetProgramiv(m_yuv_program, GL_LINK_STATUS, &status);
    if (!status) {
        char log[512];
        glGetProgramInfoLog(m_program, sizeof(log), nullptr, log);
        LOG_ERR("YUV program linking failed: {}", log);
        return;
    }
    glDeleteShader(vs);
    glDeleteShader(ps);

    m_pos_loc = glGetAttribLocation(m_program, "aPosition");
    m_tex_loc = glGetAttribLocation(m_program, "aTexCoord");
    m_yuv_y_loc = glGetUniformLocation(m_yuv_program, "uTextureY");
    m_yuv_u_loc = glGetUniformLocation(m_yuv_program, "uTextureU");
    m_yuv_v_loc = glGetUniformLocation(m_yuv_program, "uTextureV");

    float vertices[] = {
        -1.0f,  1.0f, 0.0f, 0.0f, 0.0f, // Top-left
         1.0f,  1.0f, 0.0f, 1.0f, 0.0f, // Top-right
        -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, // Bottom-left
         1.0f, -1.0f, 0.0f, 1.0f, 1.0f  // Bottom-right
    };
    uint32_t indices[] = { 0, 1, 2, 1, 3, 2 };

    glGenBuffers(1, &m_vbo);
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glGenBuffers(1, &m_ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glGenTextures(1, &m_texture);
    glBindTexture(GL_TEXTURE_2D, m_texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glGenTextures(1, &m_texture_y);
    glBindTexture(GL_TEXTURE_2D, m_texture_y);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glGenTextures(1, &m_texture_u);
    glBindTexture(GL_TEXTURE_2D, m_texture_u);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glGenTextures(1, &m_texture_v);
    glBindTexture(GL_TEXTURE_2D, m_texture_v);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    m_gl_initialized = true;
}

void OpenGLVideoRenderer::PaintGL() {
    std::lock_guard<std::mutex> lock(m_mutex);
    if (!m_is_rendering) {
        LOG_INF("Renderer is not rendering, skipping frame");
        return;
    }

#ifdef _WIN32
    if (!wglMakeCurrent(m_hdc, m_hglrc)) {
        LOG_ERR("wglMakeCurrent failed");
        return;
    }
#endif
#ifdef __ANDROID__
    if (!eglMakeCurrent(m_egl_display, m_egl_surface, m_egl_surface, m_egl_context)) {
        LOG_ERR("eglMakeCurrent failed");
        return;
    }
#endif
#ifdef __APPLE__
    [EAGLContext setCurrentContext:m_eagl_context];
    glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
#endif

    glViewport(0, 0, m_width, m_height);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    RenderCommand cmd;
    bool has_command = false;
    {
        std::lock_guard<std::mutex> lock(m_command_mutex);
        if (!m_command_queue.empty()) {
            cmd = m_command_queue.front();
            m_command_queue.pop();
            has_command = true;
        }
    }

    if (has_command) {
        RenderFrame(cmd);
    }

#ifdef _WIN32
    SwapBuffers(m_hdc);
#endif
#ifdef __ANDROID__
    if (m_egl_display != EGL_NO_DISPLAY) {
        eglSwapBuffers(m_egl_display, m_egl_surface);
    }
#endif
#ifdef __APPLE__
    glBindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer);
    [m_eagl_context presentRenderbuffer:GL_RENDERBUFFER];
#endif
}

void OpenGLVideoRenderer::ResizeGL(int w, int h) {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_width = w;
    m_height = h;
    LOG_INF("Window resized: {}x{}", w, h);
}

void OpenGLVideoRenderer::RenderFrame(const RenderCommand& cmd) {
    if (cmd.use_yuv && cmd.yuv_buffer) {
        UpdateYUVTextures(cmd.yuv_buffer);
        glUseProgram(m_yuv_program);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, m_texture_y);
        glUniform1i(m_yuv_y_loc, 0);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, m_texture_u);
        glUniform1i(m_yuv_u_loc, 1);
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, m_texture_v);
        glUniform1i(m_yuv_v_loc, 2);
    } else if (!cmd.bgra_data.empty()) {
        UpdateTexture(cmd.bgra_data.data(), cmd.width, cmd.height);
        glUseProgram(m_program);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, m_texture);
    }
#ifdef __ANDROID__
    else if (cmd.native_buffer) {
        UpdateAndroidTexture(cmd.native_buffer);
        glUseProgram(m_program);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, m_texture);
    }
#endif
#ifdef __APPLE__
    else if (cmd.pixel_buffer) {
        UpdateIOSTexture(cmd.pixel_buffer);
        glUseProgram(m_program);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, m_texture);
    }
#endif
    else {
        LOG_INF("No valid frame data, skipping render");
        return;
    }

    glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo);
    glEnableVertexAttribArray(m_pos_loc);
    glVertexAttribPointer(m_pos_loc, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), nullptr);
    glEnableVertexAttribArray(m_tex_loc);
    glVertexAttribPointer(m_tex_loc, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
    glFlush();
}

void OpenGLVideoRenderer::UpdateTexture(const uint8_t* bgra_data, int width, int height) {
    if (m_texture_width != width || m_texture_height != height) {
        m_texture_width = width;
        m_texture_height = height;
        glBindTexture(GL_TEXTURE_2D, m_texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    }
    glBindTexture(GL_TEXTURE_2D, m_texture);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bgra_data);
}

void OpenGLVideoRenderer::UpdateYUVTextures(const webrtc::I420BufferInterface* buffer) {
    int width = buffer->width();
    int height = buffer->height();
    if (m_texture_width != width || m_texture_height != height) {
        m_texture_width = width;
        m_texture_height = height;
        glBindTexture(GL_TEXTURE_2D, m_texture_y);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);
        glBindTexture(GL_TEXTURE_2D, m_texture_u);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width / 2, height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);
        glBindTexture(GL_TEXTURE_2D, m_texture_v);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width / 2, height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);
    }
    glBindTexture(GL_TEXTURE_2D, m_texture_y);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, buffer->DataY());
    glBindTexture(GL_TEXTURE_2D, m_texture_u);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_LUMINANCE, GL_UNSIGNED_BYTE, buffer->DataU());
    glBindTexture(GL_TEXTURE_2D, m_texture_v);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_LUMINANCE, GL_UNSIGNED_BYTE, buffer->DataV());
}

#ifdef __ANDROID__
void OpenGLVideoRenderer::UpdateAndroidTexture(ANativeWindow* buffer) {
    EGLImage image = eglCreateImageKHR(m_egl_display, nullptr, EGL_NATIVE_BUFFER_ANDROID, buffer, nullptr);
    glBindTexture(GL_TEXTURE_2D, m_texture);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
    eglDestroyImageKHR(m_egl_display, image);
}
#endif

#ifdef __APPLE__
void OpenGLVideoRenderer::UpdateIOSTexture(CVPixelBufferRef pixel_buffer) {
    CVOpenGLESTextureRef texture;
    CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, nullptr, pixel_buffer, nullptr, GL_TEXTURE_2D, &texture);
    glBindTexture(GL_TEXTURE_2D, CVOpenGLESTextureGetName(texture));
    CFRelease(texture);
}
#endif

void OpenGLVideoRenderer::Cleanup() {
    LOG_INF("Cleaning up OpenGLVideoRenderer resources");
    if (!m_gl_initialized) return;

#ifdef _WIN32
    if (!wglMakeCurrent(m_hdc, m_hglrc)) {
        LOG_ERR("wglMakeCurrent failed during cleanup");
    }
#endif
#ifdef __ANDROID__
    if (!eglMakeCurrent(m_egl_display, m_egl_surface, m_egl_surface, m_egl_context)) {
        LOG_ERR("eglMakeCurrent failed during cleanup");
    }
#endif
#ifdef __APPLE__
    [EAGLContext setCurrentContext:m_eagl_context];
#endif

    glDeleteTextures(1, &m_texture);
    glDeleteTextures(1, &m_texture_y);
    glDeleteTextures(1, &m_texture_u);
    glDeleteTextures(1, &m_texture_v);
    glDeleteBuffers(1, &m_vbo);
    glDeleteBuffers(1, &m_ibo);
    glDeleteProgram(m_program);
    glDeleteProgram(m_yuv_program);
    m_texture = m_texture_y = m_texture_u = m_texture_v = m_vbo = m_ibo = m_program = m_yuv_program = 0;

#ifdef _WIN32
    if (m_hglrc) {
        wglMakeCurrent(nullptr, nullptr);
        wglDeleteContext(m_hglrc);
        m_hglrc = nullptr;
    }
    if (m_hdc) {
        ReleaseDC(nullptr, m_hdc);
        m_hdc = nullptr;
    }
#endif
#ifdef __ANDROID__
    if (m_egl_surface != EGL_NO_SURFACE) {
        eglDestroySurface(m_egl_display, m_egl_surface);
        m_egl_surface = EGL_NO_SURFACE;
    }
    if (m_egl_context != EGL_NO_CONTEXT) {
        eglDestroyContext(m_egl_display, m_egl_context);
        m_egl_context = EGL_NO_CONTEXT;
    }
    if (m_egl_display != EGL_NO_DISPLAY) {
        eglTerminate(m_egl_display);
        m_egl_display = EGL_NO_DISPLAY;
    }
#endif
#ifdef __APPLE__
    if (m_framebuffer) {
        glDeleteFramebuffers(1, &m_framebuffer);
        m_framebuffer = 0;
    }
    if (m_renderbuffer) {
        glDeleteRenderbuffers(1, &m_renderbuffer);
        m_renderbuffer = 0;
    }
    if (m_eagl_context) {
        [EAGLContext setCurrentContext:nil];
        [m_eagl_context release];
        m_eagl_context = nullptr;
    }
#endif
    m_gl_initialized = false;
}

} // namespace p2pms